package com.dding.cms.base.web;

import static com.dding.cms.base.web.ActionConstants.TEMPLATE_UPLOAD_PATH;
import static com.dding.cms.base.web.util.Utils.formatPath;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.io.FileUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.dding.core.annotation.Service;
import org.dding.core.log.ILogger;
import org.dding.core.log.LoggerFactory;
import org.dding.core.util.StringUtils;
import org.dding.mvc.ActionSupport;
import org.dding.mvc.InvocationContext;
import org.dding.mvc.annotation.ActionMethod;
import org.dding.mvc.annotation.ActionMethod.ForwardMethod;

import com.dding.cms.base.web.bo.UploadFileInvocation;
import com.dding.cms.base.web.util.Configuration;
import com.dding.cms.base.web.util.FileTreeNode;
import com.dding.cms.base.web.util.TreeNode;

@Service(name = "templateAction")
public class TemplateAction extends ActionSupport<Object> {

	private static ILogger logger = LoggerFactory
			.getLogger(TemplateAction.class);

	private static String realPath;
	private volatile AtomicBoolean init = new AtomicBoolean(false);
	private static TreeMap<String, FileTreeNode> map = new TreeMap<String, FileTreeNode>();

	private SimpleDateFormat format = new SimpleDateFormat(
			"yyyy-MM-dd HH:mm:ss");
	private FileTreeNode root = new FileTreeNode();
	ObjectMapper mapper = new ObjectMapper();

	@ActionMethod(
			name = "create_folder", successView = "list.jsp",
			httpMethod = ActionMethod.HttpMethod.POST,
			invocationClass = UploadFileInvocation.class,
			forwardMethod = ForwardMethod.FORWARD)
	public void createFolder(InvocationContext invocationContext) {
		String foldername = invocationContext.getParameterValue("foldername");
		String path = invocationContext.getParameterValue("path");
		if (!StringUtils.isEmpty(foldername) && !StringUtils.isEmpty(path)) {
			File target = new File(invocationContext.getWebPath(), path
					+ File.separator + foldername);
			try {
				FileUtils.forceMkdir(target);
				createNode(path, foldername, 0, true);
			} catch (IOException e) {
				String message = "create folder " + foldername + " failure.";
				logger.error(message);
				invocationContext.getPageContext().setAttribute(ERRORMESSAGE,
						message);
			}
		}
		FileTreeNode node = map.get(path);
		fillPageContext(invocationContext, path, node, true);
	}

	@ActionMethod(
			name = "upload", httpMethod = ActionMethod.HttpMethod.POST,
			forwardMethod = ForwardMethod.FLUSH,
			invocationClass = UploadFileInvocation.class)
	public void upload(InvocationContext invocationContext) {
		UploadFileInvocation invocation = (UploadFileInvocation) invocationContext
				.getInvocation();
		String fileName = invocation.getFiledata().getFilename();
		String folder = invocationContext.getParameterValue("folder");

		File file = formatPath(invocationContext.getWebPath(), folder);
		if (file == null) {
			logger.warn("can't upload file,because of folder is not exist.");
			return;
		}
		file = new File(file, fileName);
		Map<String, String> result = new HashMap<String, String>();
		if (!file.exists()) {

			try {
				FileUtils.writeByteArrayToFile(file, invocation.getFiledata()
						.getContent());
			} catch (IOException e) {
				logger.error("write upload file " + fileName + " fialue.");
			}
			createNode(folder, fileName, (double) invocation.getFiledata()
					.getContent().length / 1024L, false);
			result.put("success", "true");
		} else {
			result.put("success", "false");
		}
		ajaxJsonMessage(result, invocationContext);
	}

	@ActionMethod(
			name = "list", successView = "list.jsp", errorView = "error.html",
			httpMethod = ActionMethod.HttpMethod.ALL)
	public void list(InvocationContext invocationContext) {
		if (!init.get())
			init(invocationContext);
		String folder = invocationContext.getParameterValue("folder");
		FileTreeNode node;
		String outPath;
		String current = invocationContext.getParameterValue("current");
		if (!StringUtils.isEmpty(current)) {
			outPath = current;
		} else {
			outPath = realPath;
		}
		if (StringUtils.isEmpty(folder)) {
			node = root;
		} else {
			String up = invocationContext.getParameterValue("up");
			if ("1".equals(up)) {
				outPath = folder.substring(0,
						folder.lastIndexOf(File.separator));
			} else {
				if (!folder.equalsIgnoreCase(outPath)) {
					outPath += File.separator + folder;
				}
			}
			node = map.get(outPath);
		}

		fillPageContext(invocationContext, outPath, node, true);
	}

	@ActionMethod(
			name = "asyn", successView = "list.do", errorView = "error.html",
			httpMethod = ActionMethod.HttpMethod.ALL)
	public void asyn(InvocationContext invocationContext) {
		map.clear();
		root = new FileTreeNode();
		init(invocationContext);
		fillPageContext(invocationContext, realPath, root, true);
	}

	@ActionMethod(
			name = "delete", errorView = "error.html",
			httpMethod = ActionMethod.HttpMethod.ALL,
			forwardMethod = ForwardMethod.FLUSH)
	public void delete(InvocationContext invocationContext) {
		String[] ids = invocationContext.getParameterValues("ids");
		Map<String, String> result = new HashMap<String, String>();
		if (ids.length == 0) {
			result.put(STATUS, FAILURE);
			result.put(MESSAGE, "请选择删除项.");
			ajaxJsonMessage(result, invocationContext);
			return;
		}

		result.put(STATUS, SUCCESS);
		result.put(MESSAGE, "删除成功");

		for (String id : ids) {
			TreeNode node = map.get(id);
			if (node == null)
				continue;
			File file = formatPath(invocationContext.getWebPath(), id);
			boolean ok = false;
			try {
				FileUtils.forceDelete(file);
				ok = true;
			} catch (IOException e) {
				logger.error(e, "delete file " + file + " failur.");
			}
			if (ok) {
				Map<String, List<String>> cache = new HashMap<String, List<String>>();
				deleteTreeNode(node, cache);
				for (Entry<String, List<String>> entry : cache.entrySet()) {
					FileTreeNode fileTreeNode = map.get(entry.getKey());
					if (fileTreeNode != null) {
						for (String treeId : entry.getValue()) {
							fileTreeNode.removeChildren(treeId);
						}
					}
				}
			} else {
				logger.error("delete file or directory fialure." + id);
				result.put(STATUS, FAILURE);
				result.put(MESSAGE, "删除失败:" + id);
				return;
			}
		}
		ajaxJsonMessage(result, invocationContext);
	}

	private void init(InvocationContext invocationContext) {
		String path = (String) Configuration.getBaseConfig().get(
				TEMPLATE_UPLOAD_PATH);
		if (StringUtils.isEmpty(path)) {
			throw new RuntimeException("can't find property "
					+ TEMPLATE_UPLOAD_PATH);
		} else {
			File file = formatPath(invocationContext.getWebPath(), path);
			if (file == null) {
				logger.warn("can't upload file,because of folder is not exist.");
				return;
			}
			realPath = path;
			root.setName("根目录");
			root.setId(path);
			root.setIsParent("true");
			root.setpId(realPath);
			root.setOpen(true);
			map.put(root.getId(), root);
			buildTree(file, root);
			init.compareAndSet(false, true);
		}
	}

	@ActionMethod(
			name = "tree_show", errorView = "error.html",
			httpMethod = ActionMethod.HttpMethod.ALL,
			forwardMethod = ForwardMethod.FLUSH)
	public void treeShow(InvocationContext invocationContext) {
		if (!init.get())
			init(invocationContext);
		String node = invocationContext.getParameterValue("node");
		StringWriter sw = new StringWriter();
		try {
			if (StringUtils.isEmpty(node)) {
				TreeNode result = getOnlyParent(root);
				mapper.writeValue(sw, result);
			} else {
				List<TreeNode> result = getOnlyChildren(map.get(node));
				mapper.writeValue(sw, result);
			}
		} catch (Exception e) {
			logger.error(e, "can't generat json data");
		}
		ajaxJsonMessage(sw.toString(), invocationContext);
	}

	@ActionMethod(
			name = "tree", errorView = "error.html",
			httpMethod = ActionMethod.HttpMethod.GET,
			forwardMethod = ForwardMethod.FLUSH)
	public void tree(InvocationContext invocationContext) {
		if (!init.get())
			init(invocationContext);
		StringWriter sw = new StringWriter();
		try {
			mapper.writeValue(sw, root);
		} catch (Exception e) {
			logger.error(e, "can't generat json data");
		}
		ajaxJsonMessage(sw.toString(), invocationContext);
	}

	private TreeNode getOnlyParent(TreeNode root) {

		if (root == null)
			return root;
		TreeNode copy = new TreeNode(root);
		copy.getChildren().clear();
		List<TreeNode> children = root.getChildren();
		if (children.size() == 0) {
			copy.setUserdata("end");
		}
		for (TreeNode child : children) {
			if ("true".equalsIgnoreCase(child.getIsParent())) {
				copy.addChildren(getOnlyParent(child));
			}
		}
		return copy;
	}

	private List<TreeNode> getOnlyChildren(TreeNode node) {

		List<TreeNode> result = new ArrayList<TreeNode>();
		if (node == null)
			return result;
		for (TreeNode child : node.getChildren()) {
			if ("true".equalsIgnoreCase(child.getIsParent()))
				continue;
			result.add(child);
		}

		return result;
	}

	private void buildTree(File footFile, FileTreeNode parent) {
		File[] files = footFile.listFiles();
		for (File f : files) {
			String path = null;
			try {
				path = f.getCanonicalPath();
			} catch (IOException e) {
			}
			int index = path.indexOf(realPath);
			if (index <= -1 && StringUtils.isEmpty(path)) {
				throw new RuntimeException("template folder can't be load.");
			}
			String id = path.substring(index);
			FileTreeNode node = new FileTreeNode(id, f.getName());
			Date date = new Date(f.lastModified());
			node.setTime(format.format(date));
			node.setpId(parent.getId());
			if (f.isDirectory()) {
				buildTree(f, node);
				node.setIsParent("true");
				parent.addSize(node.getSize());
				node.setOpen(false);
			} else {
				node.setSize((double) f.length() / 1024L);
				node.setIsParent("false");
				node.setOpen(false);
				parent.addSize((double) f.length() / 1024L);
			}
			parent.addChildren(node);
			map.put(node.getId(), node);
		}
	}

	private void createNode(String folder, String name, double size,
			boolean isFolder) {
		long time = System.currentTimeMillis();
		if (!StringUtils.isEmpty(folder)) {
			FileTreeNode parent = map.get(folder);
			if (parent != null) {
				FileTreeNode child = new FileTreeNode(folder + File.separator
						+ name, name);
				child.setOpen(false);
				child.setIsParent(Boolean.toString(isFolder));
				child.setSize(size);
				child.setTime(format.format(time));
				child.setpId(parent.getId());
				parent.addChildren(child);
				parent.addSize(size);
				map.put(child.getId(), child);
			} else {
				logger.warn("cache FileNode is not inconsistent with disk.");
			}
		} else {
			logger.warn("can't find folder " + folder);
		}
	}

	private void fillPageContext(InvocationContext invocationContext,
			String path, TreeNode node, boolean sort) {

		invocationContext.getPageContext().setAttribute("folder", path);
		invocationContext.getPageContext().setAttribute("realPath", realPath);
		if (node != null) {
			List<TreeNode> nodes = node.getChildren();
			if (nodes != null && sort)
				Collections.sort(nodes, new FileComparator());
			invocationContext.getPageContext().setAttribute("nodes", nodes);
		}
	}

	private void deleteTreeNode(TreeNode treeNode,
			Map<String, List<String>> cache) {
		if ("true".equalsIgnoreCase(treeNode.getIsParent())) {
			for (TreeNode child : treeNode.getChildren()) {
				deleteTreeNode(child, cache);
			}
		}
		map.remove(treeNode.getId());
		TreeNode parent = map.get(treeNode.getpId());
		if (parent != null) {
			List<String> children = cache.get(parent.getId());
			if (children == null)
				children = new ArrayList<String>();
			children.add(treeNode.getId());
			cache.put(parent.getId(), children);
		}
	}

	class FileComparator implements Comparator<TreeNode> {

		@Override
		public int compare(TreeNode o1, TreeNode o2) {
			if ("true".equalsIgnoreCase(o1.getIsParent())
					&& "false".equalsIgnoreCase(o2.getIsParent()))
				return -1;
			if ("false".equalsIgnoreCase(o1.getIsParent())
					&& "true".equalsIgnoreCase(o2.getIsParent()))
				return 1;
			return 0;
		}
	};
}
